package ch.zhaw.cctd.logic.event;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zhaw.cctd.domain.player.Player;

/**
 * Handhabt alle EventListener und der Aufruf der unterschiedlichen EventListener.
 * @author Benjamin Felder
 */
public class EventHandlerImpl extends UnicastRemoteObject implements EventHandler, Serializable {

	private static final long serialVersionUID = -4780213896616901012L;
	private static final Logger logger = LoggerFactory.getLogger(EventHandlerImpl.class);
	
	public EventHandlerImpl() throws RemoteException {
		super();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addChatEventListener(final ChatEventListener l) throws RemoteException {
		logger.trace("Registering new ChatEventListener");
		this.add(ChatEventListener.class, l);
		
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeChatEventListener(final ChatEventListener l)  throws RemoteException {
		logger.trace("Removing ChatEventListener");
		this.remove(ChatEventListener.class, l);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispatchChatMessage(Player origin, String message) throws RemoteException {
		logger.trace("dispatching ChatEventListener#onMessage({}, {})", origin, message);
		this.dispatch(ChatEventListener.class, new EventHandlerImpl.CallBack<ChatEventListener>() {
			private static final long serialVersionUID = 2314861000094401522L;
			@Override
			public void call(ChatEventListener l, Object... params)  throws RemoteException {
				l.onMessage(new ChatEvent(((Player)params[0]).getName(), (String)params[1]));
			}
		}, origin, message);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addLobbyEventListener(final LobbyEventListener l) throws RemoteException {
		logger.trace("Registering new  LobbyEventListener");
		this.add(LobbyEventListener.class, l);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeLobbyEventListener(final LobbyEventListener l) throws RemoteException {
		logger.trace("Removing LobbyEventListener");
		this.remove(LobbyEventListener.class, l);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispatchLobbyReady(final Player origin) throws RemoteException {
		this.dispatchLobbyReady(origin, true);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispatchLobbyUnready(final Player origin) throws RemoteException {
		this.dispatchLobbyReady(origin, false);
	}
	
	/*
	 * the actual dispatching of (un)ready events
	 */
	private void dispatchLobbyReady(final Player origin, final boolean ready) throws RemoteException {
		logger.trace("dispatching LobbyEventListener#userReady(ready={})", ready);
		this.dispatch(LobbyEventListener.class, new EventHandlerImpl.CallBack<LobbyEventListener>() {
			private static final long serialVersionUID = -2435549292173192775L;
			@Override
			public void call(LobbyEventListener l, Object... params) throws RemoteException {
				LobbyEvent event = null;
				Player p = (Player)params[0];
				if ((Boolean)params[1])
					event = new LobbyEvent(LobbyEvent.LobbyAction.READY, p.getPlayerId(), p.getName());
				else
					event = new LobbyEvent(LobbyEvent.LobbyAction.UNREADY, p.getPlayerId(), p.getName());
				l.userReady(event);
			}
		}, origin, ready);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispatchLobbyJoined(final Player origin) throws RemoteException {
		this.dispatchLobbyJoined(origin, true);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispatchLobbyLeft(final Player origin) throws RemoteException {
		this.dispatchLobbyJoined(origin, false);
	}
	
	/*
	 * actual dispatching of joined/left event
	 */
	private void dispatchLobbyJoined(final Player origin, final boolean joined) throws RemoteException {
		logger.trace("dispatching LobbyEventListener#userJoined(joined={})", joined);
		this.dispatch(LobbyEventListener.class, new EventHandlerImpl.CallBack<LobbyEventListener>() {
			private static final long serialVersionUID = -2435549292173192775L;
			@Override
			public void call(LobbyEventListener l, Object... params) throws RemoteException {
				LobbyEvent event;
				Player p = (Player)params[0];
				if ((Boolean)params[1])
					event = new LobbyEvent(LobbyEvent.LobbyAction.JOINED, p.getPlayerId(), p.getName());
				else
					event = new LobbyEvent(LobbyEvent.LobbyAction.LEFT, p.getPlayerId(), p.getName());
				l.userJoined(event);
			}
			
		}, origin, joined);
	}
	
	/**
	 * Dispatch Game Start Event
	 */
	@Override
	public void dispatchLobbyGameStart(final Player origin) throws RemoteException {
		logger.trace("dispatching LobbyEventListener#GameStart()");
		this.dispatch(LobbyEventListener.class, new EventHandlerImpl.CallBack<LobbyEventListener>() {
			private static final long serialVersionUID = -2435549292173192775L;
			@Override
			public void call(LobbyEventListener l, Object... params) throws RemoteException {
				Player p = (Player)params[0];
				LobbyEvent event = new LobbyEvent(LobbyEvent.LobbyAction.GAMESTART, p.getPlayerId(), p.getName());
				l.gameStart(event);
			}
			
		}, origin);
	}

	
	/*
	 * For the internal calling of EventListener. 
	 * This way for every Listener a own instance of Event can be created.
	 * @param <T>	the type of EventListener, which will be created.
	 */
	private abstract class CallBack<T extends EventListener> implements Serializable {
		private static final long serialVersionUID = -1L;
		public abstract void call(T l, Object... params) throws RemoteException;
	}
	
	/*
	 * General method to invoke all the EventListeners of a specific type.
	 */
	@SuppressWarnings("unchecked")
	private <T extends EventListener >void dispatch(Class<T> c, EventHandlerImpl.CallBack<T> callback, Object... params) throws RemoteException {
		for (int i = listenerList.length-2;i>=0;i-=2) {
			if (listenerList[i]==c) {
				logger.trace("Calling {}", i);
				callback.call((T)listenerList[i+1], params);
			}
		}
	}
	
	private transient Object[] listenerList = new Object[0];
	
	/*
	 * General method to add a EventListener.
	 */
	private synchronized <T extends EventListener> void add(Class<T> c, T l)  throws RemoteException {
		if (l == null) return;
		if (!c.isInstance(l)) throw new IllegalArgumentException("Listener "+l+"not of type "+c);
	
		if (listenerList == null || listenerList.length == 0) {
			listenerList = new Object[] { c, l };
		} else {
			int length = listenerList.length;
			Object[] tmp = new Object[length+2];
			System.arraycopy(listenerList, 0, tmp, 0, length);
			tmp[length] = c;
			tmp[length+1] = l;
			listenerList = tmp;
		}
	}
	
	/*
	 * General method to remove an EventListener.
	 */
	private synchronized <T extends EventListener> void remove(Class<T> c, T l)  throws RemoteException {
		if (l == null) return;
		if (!c.isInstance(l)) throw new IllegalArgumentException("Listener "+l+"not of type "+c);
		int index = -1;
		for (int i = listenerList.length-2; i>=0; i-=2) {
			if ((listenerList[i]==c) && (listenerList[i+1].equals(l))) {
				index = i;
				break;
			}
		}

		if (index != -1) {
			Object[] tmp = new Object[listenerList.length-2];
			System.arraycopy(listenerList, 0, tmp, 0, index);
			if (index < tmp.length)
				System.arraycopy(listenerList, index+2, tmp, index, tmp.length-index);
			listenerList = tmp;
		}
	}

}
